package com.binance.gateway.filter.limit;

import com.binance.gateway.constant.GwFilterOrders;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.Rate;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimitKeyGenerator;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimitUtils;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimiter;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.support.RateLimitExceededException;
import com.netflix.zuul.context.RequestContext;
import lombok.extern.log4j.Log4j2;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UrlPathHelper;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import static com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.support.RateLimitConstants.REQUEST_START_TIME;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

/**
 * Created by Fei.Huang on 2018/8/21.
 */
@Component
@Log4j2
public class GwRateLimitPreFilter extends AbstractRateLimitFilter {

    private final RateLimiter rateLimiter;

    public GwRateLimitPreFilter(RateLimitProperties properties, RouteLocator routeLocator, UrlPathHelper urlPathHelper,
                                RateLimiter rateLimiter, RateLimitKeyGenerator rateLimitKeyGenerator, RateLimitUtils rateLimitUtils) {
        super(properties, routeLocator, urlPathHelper, rateLimitUtils, rateLimitKeyGenerator);
        this.rateLimiter = rateLimiter;
    }

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return GwFilterOrders.AUTH + 1;
    }

    @Override
    public Object run() {
        final RequestContext ctx = RequestContext.getCurrentContext();
        final HttpServletResponse response = ctx.getResponse();
        final HttpServletRequest request = ctx.getRequest();
        final Route route = route(request);

        policy(route, request).forEach(policy -> {

            String limitStr = sysConfigHelper.getCodeByDisplayName(HIGH_FREQUENCY_USER_LIMIT);
            Long limit;
            try {
                limit = Long.valueOf(limitStr);
                policy.setLimit(limit);
            } catch (Exception e) {
                limit = policy.getLimit();
            }

            final String key = super.getKey(request, route, policy);
            final Rate rate = rateLimiter.consume(policy, key, null);
            final String httpHeaderKey = key.replaceAll("[^A-Za-z0-9-.]", "_").replaceAll("__", "_");

            final Long remaining = rate.getRemaining();
//            if (limit != null) {
//                response.setHeader(HEADER_LIMIT + httpHeaderKey, String.valueOf(limit));
//                response.setHeader(HEADER_REMAINING + httpHeaderKey, String.valueOf(Math.max(remaining, 0)));
//            }

            final Long quota = policy.getQuota();
            final Long remainingQuota = rate.getRemainingQuota();
            if (quota != null) {
                request.setAttribute(REQUEST_START_TIME, System.currentTimeMillis());
//                response.setHeader(HEADER_QUOTA + httpHeaderKey, String.valueOf(quota));
//                response.setHeader(HEADER_REMAINING_QUOTA + httpHeaderKey,
//                        String.valueOf(MILLISECONDS.toSeconds(Math.max(remainingQuota, 0))));
            }

//            response.setHeader(HEADER_RESET + httpHeaderKey, String.valueOf(rate.getReset()));

            if (exceedRemaining(limit, remaining) || exceedRemainingQuota(quota, remainingQuota)) {
                ctx.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
                ctx.put(RATE_LIMIT_EXCEEDED, TRUE);
                ctx.setSendZuulResponse(false);

                log.warn("Too many request force logout, userId:{}, limitFromSysConfig:{}, limit:{}, remaining:{}, interval:{}, quota:{}, remainingQuota:{}, key:[{}]",
                        super.getUserId(request), limitStr, limit, remaining, policy.getRefreshInterval(), quota, remainingQuota, key);

                // 踢出
                super.logoutAll();

                throw new RateLimitExceededException();
            }
        });

        return null;
    }

    private boolean exceedRemaining(final Long limit, final Long remaining) {
        return limit != null && remaining < 0;
    }

    private boolean exceedRemainingQuota(final Long quota, final Long remainingQuota) {
        return quota != null && remainingQuota < 0;
    }
}
